import java.io.*;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

class Leecher extends Thread
{

	private Socket socketPeer = null;
	private InputStream inB;
	private String key;
	private Peer peer;

	
	public Leecher(String key, Peer p)
	{
		super();
		this.key = key;
		this.peer = p;
		
		synchronized(Bdd.LEECH_LIST)
		{
			if(!Bdd.LEECH_LIST.containsKey(key))
			{
				FileData ot = Bdd.ON_TRACKER.get(this.key);
				FileData fd = new FileData(ot.getName(), ot.getSize(), ot.getSizePieces(), this.key);
				Bdd.LEECH_LIST.put(this.key, fd);
			}
		}
	}
	
	
	public void run()
	{
		try
		{
			this.connectToPeer();
		
	
			//Envoie un "interested $key"
			this.sendInterested(this.key);
		
			// Reçoit un bufferMap et récupère le vector d'index pour les pieces disponible
			Vector<Integer> v_dispo = Tools.getIndexFromBufferMap(this.waitForBufferMap());
			Tools.traceFluxReceive("leecher : "+v_dispo.toString());
	
			// Envoie un "getpieces $key [i1, i2, ...in]", avec les i étant les pieces à télécharger
			this.sendGetPieces(this.key, v_dispo);
		
			// Reçoit "data $key [i1:p1 ...]"
			this.receiveParts(v_dispo);
			Tools.traceFluxReceive("leecher : Parts");
			
		}catch(IOException e){}
		
	}
	
	
	private void sendInterested(String key)
	{
		this.sendToPeer(Tools.getStringInterested(key));
	}


	private void sendGetPieces(String key, Vector<Integer> v_index)
	{
		this.sendToPeer(Tools.getStringPieces(key, v_index));
	}


	/*private void sendHave(String key, String binaire)
	{
		this.sendToPeer(Tools.getStringHave(key, binaire));
	}*/
	
	
	private void connectToPeer()
	{
		Socket sockcli = null;

		try
		{
			sockcli = new Socket(this.peer.getIp(), this.peer.getPort());
			this.socketPeer = sockcli;
			this.inB = this.socketPeer.getInputStream();
		}
		catch (IOException e){}
	}
	
	
	private void sendToPeer(String s)
	{
		if(this.socketPeer == null || !this.socketPeer.isConnected())
		{
			System.out.println("Socket not connected.");
			return;
		}
		
		try
		{
			PrintWriter send_s = new PrintWriter(new BufferedWriter(new OutputStreamWriter(this.socketPeer.getOutputStream())), true);
			send_s.print(s+"\n");
			send_s.flush();
			Tools.traceFluxSend("leecher : "+s);
		}
		catch(IOException e){}
	}


	private void receiveParts(Vector<Integer> indexes) throws IOException
	{
		HashMap<Integer, byte[]> pieces = new HashMap<Integer, byte[]>();
		FileData fd = Bdd.ON_TRACKER.get(this.key);
		
		// Prefix: data 8905e92afeb80fc7722ec89eb0bf0966 [
		String prefixString = "data " + this.key + " [";
		byte[] prefixBytes = new byte[prefixString.length()];
		while (this.inB.available() < prefixString.length()); // Wait for global prefix
		this.inB.read(prefixBytes, 0, prefixString.length());
	
		for (int i=0; i<indexes.size(); i++) 
		{
			String separatorString = indexes.get(i) + ":";
			byte[] separatorBytes = new byte[separatorString.length()];
			while (this.inB.available() < separatorString.length()); 
			this.inB.read(separatorBytes, 0, separatorString.length());
		
			// Piece data: %pieceX%
			int pieceSize = ((indexes.get(i)+1) == fd.getNbPieces()) ? fd.sizeLastPiece() : fd.getSizePieces();
			byte[] pieceData = new byte[pieceSize];
			while (this.inB.available() < pieceSize);
			this.inB.read(pieceData, 0, pieceSize);
		
			// Add piece to pieces array
			pieces.put(indexes.get(i), pieceData);
		
			// Trailing space
			if (i < indexes.size() - 1) {
				while (this.inB.available() < 1);
				this.inB.read();
			}
		}
	
		this.writeInFile(pieces, fd);
	}


	synchronized private void writeInFile(HashMap<Integer, byte[]> pieces, FileData fd) throws IOException
	{
		RandomAccessFile file = new RandomAccessFile(Config.DIR_LEECH+"/"+fd.getName(), "rw");
		for (Map.Entry<Integer, byte[]> entry : pieces.entrySet()) 
		{
			file.seek((entry.getKey()) * fd.getSizePieces());
  			file.write(entry.getValue());
  			Bdd.LEECH_LIST.get(this.key).refreshBuffer(entry.getKey());
		}
		file.close();
	}
	

	private byte[] waitForBufferMap()
	{
		try
		{
			String prefixString = "have "+this.key+" ";
			byte[] prefixBytes = new byte[prefixString.length()];
			while (this.inB.available() < prefixBytes.length);
			this.inB.read(prefixBytes, 0, prefixBytes.length);
		
			int buffSize = Bdd.ON_TRACKER.get(this.key).buffMapSize();
			byte[] bufferMap = new byte[buffSize];
			while (this.inB.available() < bufferMap.length);
			this.inB.read(bufferMap, 0, bufferMap.length);
			this.inB.skip(1);
		
			return bufferMap;
		}
		catch(IOException e){ return null; }
	}

}
